
#include <jni.h>
#include <pthread.h>
#include "usbhid.h"
#include "log.h"


#define	VENDOR_ID			0xFFFF
#define	PRODUCT_ID			0x1200
#define	INTERFACE_NUM		1

#define	PKT_MAX_LEN			64
#define	PKT_HEAD_LEN		4
#define	PKT_DATA_MAX_LEN	60//(PKT_MAX_LEN - PKT_HEAD_LEN)

#define	PKT_POS_RID			0
#define	PKT_POS_CMD			1
#define	PKT_POS_DEV			2
#define	PKT_POS_DLEN		3
#define	PKT_POS_DATA		4

#define	PKT_REPORT_ID		0x21	// <usbd_conf.h> HID_REPORT_ID
#define	PKT_MAX_DEVICE		5		// touch(0), camera1(1), camera2(2), camera3(3), camera4(4)
#define	PKT_EVT_START		0xE0
#define	PKT_MAX_EVT_LEN		2500	// sideview size = 100x25

#define	JNI_VERSION			JNI_VERSION_1_6
#define	JNI_CLASS_NAME		"kr/co/bnsmedia/touchconfig/MyApp"
#define	JNI_CALLBACK_NAME	"usbhidCallback"
#define	JNI_CALLBACK_SIG	"(IIII[B)V"	// status, cmd, dev, dlen, data[]
#define	JNI_CMDBUF_SIZE		PKT_DATA_MAX_LEN
#define	JNI_EVTBUF_SIZE		4096


static JavaVM* glpVM = NULL;
static jclass gClass = NULL;
static jmethodID gMethodID = NULL;
static jobject gCmdBuf = NULL;
static jbyte* gCmdBufPinned = NULL;
static jobject gEvtBuf[PKT_MAX_DEVICE] = {0,0,0,0,0};
static jbyte* gEvtBufPinned[PKT_MAX_DEVICE] = {0,0,0,0,0};
static jint gEvtBufCount[PKT_MAX_DEVICE] = {0,0,0,0,0};
static usbhid_device *gDevice = NULL;
static int gnThreadAlive = 0;
static pthread_t gtThread = 0;


static void read_callback(struct libusb_transfer *transfer)
{
	JNIEnv* env = NULL;

	if (transfer->status == LIBUSB_TRANSFER_COMPLETED)
	{
		int reportid = (int)(transfer->buffer[PKT_POS_RID] & 0xFF);
		int command = (int)(transfer->buffer[PKT_POS_CMD] & 0xFF);
		int device = (int)(transfer->buffer[PKT_POS_DEV] & 0xFF);
		int dataLen = (int)(transfer->buffer[PKT_POS_DLEN] & 0x7F);

		// 0(rid), 1(cmd), 2(dev), 3(dlen), 4(data)...
		if (PKT_REPORT_ID != reportid) {
			E("%s : REPORT_ID(0x%02X) ERROR %d", __FUNCTION__, reportid, gEvtBufCount[device]);
		} else if (0 == command) {
			E("%s : COMMAND(0x00) ERROR", __FUNCTION__);
		} else if (PKT_MAX_DEVICE <= device) {
			E("%s : DEVICE_ID(0x%02X) ERROR", __FUNCTION__, device);
		} else if (PKT_DATA_MAX_LEN < dataLen) {
			E("%s : DATA_LEN(0x%02X) ERROR", __FUNCTION__, dataLen);
		} else {
			if (PKT_EVT_START <= transfer->buffer[PKT_POS_CMD]) {
				int isEnd = !(transfer->buffer[PKT_POS_DLEN] & 0x80) ? 1 : 0;
				
				if (dataLen) {
					memcpy(&gEvtBufPinned[device][gEvtBufCount[device]], &transfer->buffer[PKT_POS_DATA], dataLen);
					gEvtBufCount[device] += dataLen;
				}
				
				if (isEnd) {
					(*glpVM)->AttachCurrentThread(glpVM, &env, NULL);
					(*env)->ReleaseByteArrayElements(env, (jbyteArray)gEvtBuf[device], gEvtBufPinned[device], JNI_COMMIT);
					(*env)->CallStaticVoidMethod(env, gClass, gMethodID,
						transfer->status, command, device, gEvtBufCount[device], (jbyteArray)gEvtBuf[device]);
					(*glpVM)->DetachCurrentThread(glpVM);

					gEvtBufCount[device] = 0;
				} else if (PKT_MAX_EVT_LEN < gEvtBufCount[device]) {
					E("%s : PKT_MAX_EVT_LEN(%d) ERROR", __FUNCTION__, gEvtBufCount[device]);

					gEvtBufCount[device] = 0;
				}
			} else {
				if (dataLen) {
					memcpy(gCmdBufPinned, &transfer->buffer[PKT_POS_DATA], dataLen);
				}

				(*glpVM)->AttachCurrentThread(glpVM, &env, NULL);
				(*env)->ReleaseByteArrayElements(env, (jbyteArray)gCmdBuf, gCmdBufPinned, JNI_COMMIT);
				(*env)->CallStaticVoidMethod(env, gClass, gMethodID,
					transfer->status, command, device, dataLen, (jbyteArray)gCmdBuf);
				(*glpVM)->DetachCurrentThread(glpVM);
			}
		}
	}
	else
	{
		(*glpVM)->AttachCurrentThread(glpVM, &env, NULL);
		(*env)->CallStaticVoidMethod(env, gClass, gMethodID, transfer->status, 0, 0, 0, NULL);
		(*glpVM)->DetachCurrentThread(glpVM);
	}

	/* Re-submit the transfer object. */
	libusb_submit_transfer(transfer);
}
static void* pfn_thread(void* data)
{
	I("%s : Start", __FUNCTION__);

	while (gnThreadAlive)
	{
		if (0 != usbhid_thread(gDevice))
		{
			break;
		}
	}

	I("%s : Stop", __FUNCTION__);
	//pthread_exit(&result);
}


jint usbhidStart_Native(JNIEnv* env, jobject thiz)
{
	jint result = -1;
	jclass cls = NULL;
	jbyteArray localRef = NULL;
	int i;
	I("%s : Start", __FUNCTION__);

	cls = (*env)->FindClass(env, JNI_CLASS_NAME);
	if (NULL == cls)
	{
		E("%s : FindClass failed", __FUNCTION__);
		goto goto_error;
	}

	gClass = (jclass)(*env)->NewGlobalRef(env, cls);
	if (NULL == gClass)
	{
		E("%s : NewGlobalRef failed", __FUNCTION__);
		goto goto_error;
	}

	gMethodID = (*env)->GetStaticMethodID(env, cls, JNI_CALLBACK_NAME, JNI_CALLBACK_SIG);
	if (NULL == gMethodID)
	{
		E("%s : GetStaticMethodID failed", __FUNCTION__);
		goto goto_error;
	}
	
	if (localRef = (*env)->NewByteArray(env, JNI_CMDBUF_SIZE))
	{
		if (gCmdBuf = (*env)->NewGlobalRef(env, localRef))
		{
			jboolean isCopy = JNI_FALSE;
			gCmdBufPinned = (*env)->GetByteArrayElements(env, (jbyteArray)gCmdBuf, &isCopy);
		}
		
		 (*env)->DeleteLocalRef(env, localRef);
	}

	for (i=0; i<PKT_MAX_DEVICE; i++)
	{
		if (localRef = (*env)->NewByteArray(env, JNI_EVTBUF_SIZE))
		{
			if (gEvtBuf[i] = (*env)->NewGlobalRef(env, localRef))
			{
				jboolean isCopy = JNI_FALSE;
				gEvtBufPinned[i] = (*env)->GetByteArrayElements(env, (jbyteArray)gEvtBuf[i], &isCopy);
			}
			
			 (*env)->DeleteLocalRef(env, localRef);
		}
	}

	gDevice = usbhid_open(VENDOR_ID, PRODUCT_ID, INTERFACE_NUM, read_callback);
	if (NULL == gDevice)
	{
		E("%s : usbhid_open failed", __FUNCTION__);
		goto goto_error;
	}

	gnThreadAlive = 1;
	result = pthread_create(&gtThread, NULL, pfn_thread, (void*)&gnThreadAlive);
	if (0 > result)
	{
		E("%s : pthread_create failed", __FUNCTION__);
		goto goto_error;
	}

	result = 0;

goto_error:
	if (0 != result)
	{
		if (gDevice)
		{
			usbhid_close(gDevice);
			gDevice = NULL;
		}

		for (i=0; i<PKT_MAX_DEVICE; i++)
		{
			if (gEvtBuf[i])
			{
				(*env)->ReleaseByteArrayElements(env, (jbyteArray)gEvtBuf[i], gEvtBufPinned[i], JNI_ABORT);
				(*env)->DeleteGlobalRef(env, gEvtBuf[i]);
				gEvtBuf[i] = NULL;
			}
		}

		if (gCmdBuf)
		{
			(*env)->ReleaseByteArrayElements(env, (jbyteArray)gCmdBuf, gCmdBufPinned, JNI_ABORT);
			(*env)->DeleteGlobalRef(env, gCmdBuf);
			gCmdBuf = NULL;
		}

		if (gClass)
		{
			(*env)->DeleteGlobalRef(env, gClass);
			gClass = NULL;
		}
	}

	I("%s : End", __FUNCTION__);
	return result;
}
void usbhidStop_Native(JNIEnv* env, jobject thiz)
{
	int i;
	I("%s : Start", __FUNCTION__);

	gnThreadAlive = 0;
	if (gtThread)
	{
		int result;
		pthread_join(gtThread, (void**)&result);
	}

	/*if (gDevice)
	{
		usbhid_close(gDevice);
		gDevice = NULL;
	}*/

	for (i=0; i<PKT_MAX_DEVICE; i++)
	{
		if (gEvtBuf[i])
		{
			(*env)->ReleaseByteArrayElements(env, (jbyteArray)gEvtBuf[i], gEvtBufPinned[i], JNI_ABORT);
			(*env)->DeleteGlobalRef(env, gEvtBuf[i]);
			gEvtBuf[i] = NULL;
		}
	}

	if (gCmdBuf)
	{
		(*env)->ReleaseByteArrayElements(env, (jbyteArray)gCmdBuf, gCmdBufPinned, JNI_ABORT);
		(*env)->DeleteGlobalRef(env, gCmdBuf);
		gCmdBuf = NULL;
	}
	
	if (gClass)
	{
		(*env)->DeleteGlobalRef(env, gClass);
		gClass = NULL;
	}

	I("%s : End", __FUNCTION__);
}

int usbhidWrite_Native(JNIEnv *env, jobject thiz, jint in_count, jbyteArray in_array)
{
	int res = -1;
	I("%s : Start", __FUNCTION__);

	if (gDevice)
	{
		jsize count = (*env)->GetArrayLength(env, in_array);
		jbyte *data = (*env)->GetByteArrayElements(env, in_array, 0);
		res = usbhid_write(gDevice, count, data);
		(*env)->ReleaseByteArrayElements(env, in_array, data, JNI_ABORT);
	}

	I("%s : End", __FUNCTION__);
	return res;
}


static JNINativeMethod methods[] = {
	{"usbhidStart", "()I", (void*)usbhidStart_Native},
	{"usbhidStop", "()V", (void*)usbhidStop_Native},
	{"usbhidWrite", "(I[B)I", (void*)usbhidWrite_Native},
};
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
	jint result = -1;
	JNIEnv* env = NULL;
	jclass cls = NULL;
	I("%s : Start", __FUNCTION__);

	if (JNI_OK != (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION))
	{
		E("%s : GetEnv failed", __FUNCTION__);
		goto goto_error;

	}

	cls = (*env)->FindClass(env, JNI_CLASS_NAME);
	if (NULL == cls)
	{
		E("%s : FindClass failed", __FUNCTION__);
		goto goto_error;
	}

	if (0 > (*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])))
	{
		E("%s : RegisterNatives failed", __FUNCTION__);
		goto goto_error;
	}

	glpVM = vm;
	result = JNI_VERSION;

goto_error:
	I("%s : End", __FUNCTION__);
	return result;
}
void JNI_OnUnload(JavaVM* vm, void* reserved)
{
	JNIEnv* env = NULL;
	jclass cls = NULL;
	I("%s : Start", __FUNCTION__);

	if (JNI_OK != (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION))
	{
		E("%s : GetEnv failed", __FUNCTION__);
		return;
	}

	usbhidStop_Native(env, NULL);

	cls = (*env)->FindClass(env, JNI_CALLBACK_NAME);
	if (NULL == cls)
	{
		E("%s : FindClass failed", __FUNCTION__);
		return;
	}

	if (0 > (*env)->UnregisterNatives(env, cls))
	{
		E("%s : UnregisterNatives failed", __FUNCTION__);
		return;
	}

	glpVM = NULL;
	gClass = NULL;
	gMethodID = NULL;
	I("%s : End", __FUNCTION__);
}
